/* 
 *  FileName: globalfifo.c - 模拟字符设备操作（书写顺序由下而上）
 *                          使用private_data,驱动可支持2个设备
 *
 *  Author: He Jingbo   jbhe@isoftstone.com
 *
 *  Date: Wed 29 Dec 2010 04:27:20 PM CST
 */

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include "globalfifo.h"

struct globalfifo_dev *globalfifo_devp; /* 设备结构体指针 */
static int globalfifo_major = GLOBALMEM_MAJOR;

/* 文件打开函数 */
int globalfifo_open(struct inode *inode, struct file *filp)
{
    /* 将设备结构体指针赋值给文件私有数据 */
    struct globalfifo_dev *dev;

    /* 
     * 由结构体的成员地址,获得结构体的地址
     * container_of(成员地址,成员所在结构体类型,成员在结构体中的变量名)
     * 从inode中获取单个设备的globalfifo_devp,
     * 全局变量globalfifo_devp保存的是两个设备结构体的起始地址 */
    dev = container_of(inode->i_cdev, struct globalfifo_dev, cdev);
    filp->private_data = dev;
    return 0;
}

/* 支持异步通知的fsync()
 * 针对应用程序中的fcntl(fd, F_GETFL)
 */
static int globalfifo_fasync(int fd, struct file *filp, int mode)
{
    struct globalfifo_dev *dev = filp->private_data;
    return fasync_helper(fd, filp, mode, &dev->async_queue);
}

/* 文件轮询函数 poll()
 * 它的作用是,在应用程序中,在非阻塞读写操作下，先用其判断现在文件能被否读写
 * 如果可以,在调用读写函数读写操作
 *
 * 需要说明的是:
 * poll_wait函数并不阻塞,真正的阻塞动作是上层select/poll完成的
 * select/poll会在一个循环中对每个需要监听的设备调用它们自己的
 * poll函数,以使得当前进程被加入各个设备的等待列表.若当前没有任何被监听
 * 的设备就绪,则内核让出cpu进入阻塞状态,schedule()返回时将再次循环检测
 * 是否有操作可以进行,如此反复；否则,若有任意一个设备就绪,
 * select/poll都立即返回.
 * 也就是说,当没有可用的设备时,应用层select/poll是会阻塞的.
 * 所以,应用层的函数带有timeout参数.
 * 当timeout时间后还没有设备/文件准备好,则返回
 */
static unsigned int globalfifo_poll(struct file *filp, poll_table *wait)
{
    unsigned int mask = 0;
    /* 获取设备结构指针 */
    struct globalfifo_dev *dev = filp->private_data;

    down(&dev->sem);

    /* 对可能引起设备文件状态(可读,可写)变化的队列,(读/写的队列)
     * 调用poll_wait函数,将对应的等待队列头(r_wait,w_wait)
     * 添加到对应的poll_table wait中 
     */
    poll_wait(filp, &dev->r_wait, wait);
    poll_wait(filp, &dev->w_wait, wait);

    /* fifo非空,表示可读 */
    if(dev->current_len != 0)
    {
        mask |= POLLIN | POLLRDNORM;
    }

    /* fifo非满,表示可写 */
    if(dev->current_len != GLOBALFIFO_SIZE)
    {
        mask |= POLLOUT | POLLWRNORM;
    }

    up(&dev->sem);
    return mask;
}

/* ioctl()函数接受MEM_CLEAR命令，将全局内存的有效数据长度清零，
 * 对于设备不支持的命令，ioctl()函数返回-EINVAL.
 */
static int globalfifo_ioctl(struct inode *inodep, struct file *filp, 
        unsigned int cmd, unsigned long arg)
{
    /* 获取设备结构体指针 */
    struct globalfifo_dev *dev = filp->private_data;

    switch (cmd)
    {
        case MEM_CLEAR:
            if(down_interruptible(&dev->sem))
            {
                /* 获取信号量失败 */
                return -ERESTARTSYS;
            }
            /* 清零全局内存 */
            memset(dev->mem, 0, GLOBALFIFO_SIZE);
            /* 释放信号量 */
            up(&dev->sem);
            printk(KERN_INFO "globalfifo is set to zero \n");
            break;

        default:
            return -EINVAL;
    }
    return 0;
}

/* 
 * seek()函数对文件定位的起始地址可以是文件头(SEEK_SEET, 0),
 * 当前位置(SEEK_CUR, 1)和文件尾(SEEK_END, 2),
 * globalfifo支持从文件开头和当前位置相对位移
 */

/* 驱动llseek函数实现,用来修改文件当前读写位置 */
static loff_t globalfifo_llseek(struct file *filp, loff_t offset, int orig)
{
    loff_t ret;
    switch (orig)
    {
        /* 从文件开头开始偏移 */
        case 0:
            if(offset < 0)
            {
                ret = -EINVAL;
                break;
            }
            if((unsigned int)offset > GLOBALFIFO_SIZE)
            {
                ret = -EINVAL;
                break;
            }

            filp->f_pos = (unsigned int) offset;
            ret = filp->f_pos;
            break;

            /* 从当前位置开始偏移 */
        case 1:
            if((filp->f_pos + offset) > GLOBALFIFO_SIZE)
            {
                ret = -EINVAL;
                break;
            }
            if((filp->f_pos + offset) < 0)
            {
                ret = -EINVAL;
                break;
            }
            
            filp->f_pos += offset;
            ret = filp->f_pos;
            break;

        default:
            ret = -EINVAL;
    }
    return ret;
}

/* 注意：参数loff_t *ppos是自动由llseek中的相对位移传过来的
 * 即这里的*ppos = (llseek中的)offset
 * 驱动写函数实现 
 */
static ssize_t globalfifo_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
    int ret = 0;
    struct globalfifo_dev *dev = filp->private_data;
    /* 定义等待队列 */
    DECLARE_WAITQUEUE(wait, current);
    
    if(down_interruptible(&dev->sem))
    {
        /* 获取信号量失败 */
        return -ERESTARTSYS;
    }
    /* 将等待队列加入到队列中 */
    add_wait_queue(&dev->w_wait, &wait);
    /* 等待FIFO非满
     * 用while表示非写不可
     */
    while(dev->current_len == GLOBALFIFO_SIZE)
    {
        if(filp->f_flags & O_NONBLOCK)
        {
            /* 如果FIFO已经满了,并且以非阻塞打开fifo,则不等待,直接退出 */
            ret = -EAGAIN;
            /* 释放信号量 */
            goto out;
        }
        /* 以阻塞形式打开fifo,改变进程状态为睡眠 */
        __set_current_state(TASK_INTERRUPTIBLE);
        /* 释放信号量 */
        up(&dev->sem);
        /* 让出CPU,调度其他程序执行 */
        schedule();

        /* 以下程序,进程醒来后才会执行
         * 因为睡眠前,程序状态设定为TASK_INTERRUPTIBLE,即浅度睡眠,因此,唤醒它的可能是信号
         */
        if(signal_pending(current))
        {
            /* 如果是因为信号唤醒,直接退出 */
            ret = -ERESTARTSYS;
            /* 移除等待队列 */
            goto out2;
        }
        /* 获取信号量,准备读取 */
        down(&dev->sem);
    }

    /* 拷贝数据到内核空间 */
    if(count > GLOBALFIFO_SIZE - dev->current_len)
    {
        count = GLOBALFIFO_SIZE - dev->current_len;
    }

    if(copy_from_user(dev->mem + dev->current_len, buf, count))
    {
        ret = -EFAULT;
        goto out;
    }
    else
    {
        dev->current_len += count;
        printk(KERN_INFO "written %d byte(s), current_len: %d\n", count, dev->current_len);
        wake_up_interruptible(&dev->r_wait);
        /* 写完成,发射可读信号 */
        if(dev->async_queue)
        {
            kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
        }
        ret = count;
    }
out:
    up(&dev->sem);
out2:
    remove_wait_queue(&dev->w_wait, &wait); /* 移除等待队列 */
    set_current_state(TASK_RUNNING);
    return ret;
}

/* 注意：参数loff_t *ppos是自动由llseek中的相对位移传过来的
 * 即这里的*ppos = (llseek中的)offset
 * 驱动读函数实现 
 */
static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
    int ret = 0;
    struct globalfifo_dev *dev = filp->private_data;

    /* 定义等待队列 */
    DECLARE_WAITQUEUE(wait, current);
    
    if(down_interruptible(&dev->sem))
    {
        /* 获取信号量失败 */
        return -ERESTARTSYS;
    }
    /* 将等待队列加入到队列中 */
    add_wait_queue(&dev->r_wait, &wait);

    /* 等待FIFO非空
     * 用wile表示非读到数据不可
    */
    while(0 == dev->current_len)
    {
        if(filp->f_flags & O_NONBLOCK)
        {
            /* 如果为空,并且以非阻塞打开fifo,则不等待,直接退出 */
            ret = -EAGAIN;
            /* 释放信号量 */
            goto out;
        }
        /* 以阻塞形式打开fifo,改变进程状态为睡眠 */
        __set_current_state(TASK_INTERRUPTIBLE);
        /* 释放信号量 */
        up(&dev->sem);
        /* 让出CPU,调度其他程序执行 */
        schedule();

        /* 以下程序,进程醒来后才会执行
         * 因为睡眠前,程序状态设定为TASK_INTERRUPTIBLE,即浅度睡眠,因此,唤醒它的可能是信号
         */
        if(signal_pending(current))
        {
            /* 如果是因为信号唤醒,直接退出 */
            ret = -ERESTARTSYS;
            /* 移除等待队列 */
            goto out2;
        }
        /* 获取信号量,准备读取 */
        down(&dev->sem);
    }

    /* 拷贝数据到用户空间 */
    if(count > dev->current_len)
    {
        count = dev->current_len;
    }

    if(copy_to_user(buf, dev->mem, count))
    {
        ret = -EFAULT;
        goto out;
    }
    else
    {
        /* fifo数据前移 */
        memcpy(dev->mem, dev->mem + count, dev->current_len - count);
        /* 有效数据长度减少 */
        dev->current_len -= count;
        printk(KERN_INFO "read %d byte(s), current_len: %d\n", count, dev->current_len);
        /* 因为在读的时候,会阻塞写操作,所以,读完以后唤醒写等待队列 */
        wake_up_interruptible(&dev->w_wait);
        ret = count;
    }

out:
    up(&dev->sem);
out2:
    /* 移除读等待队列 */
    remove_wait_queue(&dev->r_wait, &wait);
    set_current_state(TASK_RUNNING);
    return ret;
}

/* 文件释放函数 */
int globalfifo_release(struct inode *inode, struct file *filp)
{
    /* 将设备从异步通知列表中删除 */
    globalfifo_fasync(-1, filp, 0);
    return 0;
}

/* 设置设备操作函数组 */
static const struct file_operations globalfifo_fops =
{
    .owner  = THIS_MODULE,
    .open   = globalfifo_open,
    .poll   = globalfifo_poll,
    .read   = globalfifo_read,
    .write  = globalfifo_write,
    .ioctl  = globalfifo_ioctl,
    .llseek = globalfifo_llseek,
    .fasync = globalfifo_fasync,
    .release= globalfifo_release,
};

/* 初始化并添加cdev结构体 */
static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
{
    int err;
    dev_t devno = MKDEV(globalfifo_major, index);

    cdev_init(&dev->cdev, &globalfifo_fops);
    dev->cdev.owner = THIS_MODULE;
    /* 参数分别为: 设备, 设备号, 注册的设备个数 */
    err = cdev_add(&dev->cdev, devno, 1);
    if(err)
    {
        printk(KERN_NOTICE "Error %d adding globalfifo", err);
    }
}

/* globalfifo probe函数 */
static int __devinit globalfifo_probe(struct platform_device *pdev)
{
    int i, result;
    dev_t devno = MKDEV(globalfifo_major, 0);

    /* 申请字符设备驱动 */
    if(globalfifo_major)
    {
        /* 指定主设备号时，注册设备 
         */
        result = register_chrdev_region(devno, DEV_COUNT, "globalfifo");
    }
    else
    {   
        /* 未指定设备号时，动态分配设备号，并将信息存储在devno中
         * 0 表示次设备号从0开始
         */
        result = alloc_chrdev_region(&devno, 0, DEV_COUNT, "globalfifo");
        globalfifo_major = MAJOR(devno);
    }
    if(result < 0)
    {
        return result;
    }

    /* 动态申请设备结构体内存 */
    globalfifo_devp = kmalloc(DEV_COUNT*sizeof(struct globalfifo_dev), GFP_KERNEL);
    if(!globalfifo_devp)
    {
        /* 申请失败 */
        result = -ENOMEM;
        goto fail_malloc;
    }
    memset(globalfifo_devp, 0, DEV_COUNT*sizeof(struct globalfifo_dev));

    for(i = 0; i < DEV_COUNT; i++)
    {
        /* 分配到系统资源后，设置该设备 */
        globalfifo_setup_cdev(&globalfifo_devp[i], i);

        /* 初始化信号量 */
        init_MUTEX(&(globalfifo_devp[i].sem));

        /* 初始化等待队列头 */
        init_waitqueue_head(&(globalfifo_devp[i].r_wait));
        init_waitqueue_head(&(globalfifo_devp[i].w_wait));
    }
    
    return 0;

fail_malloc:
    /* 因为没有分配到资源,所以不用释放资源
     * 释放两个设备号
     */
    unregister_chrdev_region(devno, DEV_COUNT);
    return  result;
}

/* globalfifo设备驱动模块remove函数 */
static int __devexit globalfifo_remove(struct platform_device *pdev)
{
    int i;
    for(i = 0; i < DEV_COUNT; i++)
    {
        cdev_del(&(globalfifo_devp[i].cdev));                       /* 删除cdev结构 */
    }
    
    unregister_chrdev_region(MKDEV(globalfifo_major, 0), DEV_COUNT);/* 释放两个设备号 */
    kfree(globalfifo_devp);                                         /* 释放设备结构体内存 */
    return 0;
}

static struct platform_driver globalfifo_device_driver =
{
    .probe  = globalfifo_probe,
    .remove = __devexit_p(globalfifo_remove),
    .driver =
    {
        .name   = "globalfifo",
        .owner  = THIS_MODULE,
    },
};

/* platform驱动书写顺序：
 * 1、在驱动内添加platform_driver结构
 * 2、在arch/arm/mach-s3c6410/mach-smdk6410.c设备列表中
 *      添加设备
 * 3、 根据设备列表中相似设备定义的地方,去定义设备plarform_device,
 *      以及设备资源
 */
/* globalfifo驱动初始化函数 */
static int __init globalfifo_init(void)
{
    return platform_driver_register(&globalfifo_device_driver);
}

/* globalfifo驱动退出函数 */

static void __exit globalfifo_exit(void)
{
    platform_driver_unregister(&globalfifo_device_driver);
}

/* 使用函数，module_param()在模块加载时可以指定变量值
 * 如 insmod globalfifo.ko globalfifo_major=0
 * 则模块驱动中,globalfifo_major的值为0
 */
module_param(globalfifo_major, int, S_IRUGO);
module_init(globalfifo_init);
module_exit(globalfifo_exit);

MODULE_AUTHOR("Jacob Ho <jbhe@isoftstone.com>");
MODULE_LICENSE("Dual BSD/GPL");
